package com.abocode.smarten.com.abocode.smarten.client;

import com.abocode.smarten.client.netty.ClientChannelInitializer;
import com.abocode.smarten.client.netty.NamedThreadFactory;
import com.abocode.smarten.client.netty.NettyClient;
import com.abocode.smarten.client.storage.FileUtils;
import com.abocode.smarten.com.abocode.smarten.client.configuration.ConfigurationInterpolator;
import com.abocode.smarten.com.abocode.smarten.client.configuration.PropertiesReader;
import com.abocode.smarten.com.abocode.smarten.client.configuration.PropertyConverter;
import com.abocode.smarten.com.abocode.smarten.client.event.EventSource;
import com.abocode.smarten.com.abocode.smarten.client.event.EventType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.lang.text.StrSubstitutor;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * @author: guanxianfei
 * @date: 2019/7/24
 */
@Data
@AllArgsConstructor
@Slf4j
@NoArgsConstructor
public class ClientProperties extends EventSource {
   private String host="127.0.0.1";
   private  int port=8283;
   private  String projectCode="smarten";
   private  String moduleCodes="test"; //多个用逗号隔开
   private String profile="development";
   private Map<String, String> store = null;
   private StrSubstitutor strSubstitutor;
   private NettyClient client;
   private volatile boolean reloadable = true;
   private static final ExecutorService reloadExecutorService = Executors.newSingleThreadExecutor(new NamedThreadFactory("ReloadExecutorService"));
   public ClientProperties(String host, int port, String projectCode, String moduleCodes, String profile) {
      this.host = host;
      this.port = port;
      this.projectCode = projectCode;
      this.moduleCodes = moduleCodes;
      this.profile = profile;
      connectServer(host, port, projectCode, profile, moduleCodes);
      strSubstitutor = new StrSubstitutor(buildInterpolator());
   }

   public ClientProperties(final String projectCode, final String profile) {
      this.projectCode = projectCode;
      this.profile = profile;
      connectServer(host, port, projectCode, profile, moduleCodes);
      strSubstitutor = new StrSubstitutor(buildInterpolator());
   }

   protected void connectServer(String host, int port, final String projectCode, final String profile, final String moduleCodes) {
      Assert.notNull(projectCode, "连接config， projectCode不能为空");

      final String clientMsg = "config={\"projectCode\": \"" + projectCode + "\", \"profile\": \"" + profile + "\", "
              + "\"moduleCodes\": \"" + moduleCodes + "\", \"version\": \"1.1.0\"}";
      try {
         client = new NettyClient(host, port, new ClientChannelInitializer(clientMsg));
         if(client.isConnected()) {
            String message =client.receiveMessage();
            if(StringUtils.isNotBlank(message)) {
              /* String versionStr = message.substring(0, message.indexOf("\r\n"));
               log.info("加载配置信息，项目编码：{}，Profile：{}, Version：{}", projectCode, profile, versionStr.split(" = ")[1]);*/
               FileUtils.saveConfigToLocal(projectCode, profile, message);
               load(new StringReader(message), false);
            }else{
                log.warn(String.format("从服务器端获取配置信息为空，Client 请求信息为：%s" ,clientMsg));
            }
         } else {
            String message = FileUtils.getConfigFromLocal(projectCode, profile);
            if(message != null) {
//               String versionStr = message.substring(0, message.indexOf("\r\n"));
//               log.info("加载本地备份配置信息，项目编码：{}，Profile：{}, Version：{}", projectCode, profile, versionStr.split(" = ")[1]);
               load(new StringReader(message), false);
            }else{
                log.warn("本地没有备份配置数据，PropertiesConfiguration 初始化失败。");
            }
         }
         reloadExecutorService.submit(new Runnable() {
            public void run() {
               while(reloadable) {
                  try {
                     if(client.isConnected()) {
                        String message = client.receiveMessage();
                        if(message != null) {
//                           String versionStr = message.substring(0, message.indexOf("\r\n"));
//                           log.info("重新加载配置信息，项目编码：{}，Profile：{}, Version：{}", projectCode, profile, versionStr.split(" = ")[1]);
                           FileUtils.saveConfigToLocal(projectCode, profile, message);
                           load(new StringReader(message), true);
                        }
                     } else {
                        TimeUnit.SECONDS.sleep(1);
                     }
                  } catch(Exception e) {
                      log.error(e.getMessage());
                  }
               }
            }
         });
      } catch (Exception e) {
         if(client != null) {
            client.close();
         }
         throw new RuntimeException(e.getMessage(), e);
      }
   }


   protected ConfigurationInterpolator buildInterpolator() {
      ConfigurationInterpolator interpol = new ConfigurationInterpolator();
      interpol.setDefaultLookup(new StrLookup()
      {
         @Override
         public String lookup(String var)
         {
            String prop = getProperty(var);
            return (prop != null) ? prop : null;
         }
      });
      return interpol;
   }

   public Properties getProperties() {
      Properties properties = new Properties();

      for(String key : store.keySet()) {
         properties.setProperty(key, getString(key));
      }
      return properties;
   }

   public String getString(String key) {
      String value = getProperty(key);
      Object result = strSubstitutor.replace(value);
      return (result == null) ? null : result.toString();
   }
   private String getProperty(String key) {
      return store.get(key);
   }

   public void close() {
      reloadable = false;

      if(client != null && client.isConnected())
         client.close();
   }

   public void load(String config) throws RuntimeException {
      load(new StringReader(config), false);
   }

   /**
    * 加载配置文件
    * @param reload 初次初始化加载为false，服务端推送加载为true。
    */
   public void load(Reader in, boolean reload) throws RuntimeException {
      Map<String, String> tmpStore = new LinkedHashMap<String, String>();

      PropertiesReader reader = new PropertiesReader(in);
      try {
         while (reader.nextProperty()) {
            String key = reader.getPropertyName();
            String value = reader.getPropertyValue();
            tmpStore.put(key, value);
            if(reload) {
               String oldValue = store.remove(key);
               if(oldValue == null)
                  fireEvent(EventType.ADD, key, value);
               else if(!oldValue.equals(value))
                  fireEvent(EventType.UPDATE, key, value);
            }
         }

         if(reload) {
            for(String key : store.keySet()) {
               fireEvent(EventType.CLEAR, key, store.get(key));
            }
         }
      } catch (IOException ioex) {
         throw new RuntimeException(ioex);
      } finally {
         try {
            reader.close();
         } catch (IOException e) {
            log.error(e.getMessage());
         }
      }

      if(store != null)
         store.clear();

      store = tmpStore;
   }

   public static String getProjCode() {
      return System.getProperty("config.projectCode");
   }

   public static String getProfile() {
      return System.getProperty("config.profile", "development");
   }

   public static String getModules() {
      return System.getProperty("config.moduleCodes");
   }

   public static String getHost() {
      return System.getProperty("config.host", "localhost");
   }

   public static int getPort() {
      return Integer.valueOf(System.getProperty("config.port", "8283"));
   }

   public boolean getBoolean(String key) {
      Boolean b = getBoolean(key, null);
      if (b != null) {
         return b.booleanValue();
      } else {
         throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
      }
   }

   public boolean getBoolean(String key, boolean defaultValue) {
      return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue))
              .booleanValue();
   }

   public Boolean getBoolean(String key, Boolean defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toBoolean(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Boolean object", e);
         }
      }
   }

   public byte getByte(String key) {
      Byte b = getByte(key, null);
      if (b != null) {
         return b.byteValue();
      } else {
         throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
      }
   }

   public byte getByte(String key, byte defaultValue) {
      return getByte(key, new Byte(defaultValue)).byteValue();
   }

   public Byte getByte(String key, Byte defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toByte(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Byte object", e);
         }
      }
   }

   public double getDouble(String key) {
      Double d = getDouble(key, null);
      if (d != null) {
         return d.doubleValue();
      } else {
         throw new NoSuchElementException('\'' + key
                 + "' doesn't map to an existing object");
      }
   }

   public double getDouble(String key, double defaultValue) {
      return getDouble(key, new Double(defaultValue)).doubleValue();
   }

   public Double getDouble(String key, Double defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toDouble(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Double object", e);
         }
      }
   }

   public float getFloat(String key) {
      Float f = getFloat(key, null);
      if (f != null) {
         return f.floatValue();
      } else {
         throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
      }
   }

   public float getFloat(String key, float defaultValue) {
      return getFloat(key, new Float(defaultValue)).floatValue();
   }

   public Float getFloat(String key, Float defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toFloat(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Float object", e);
         }
      }
   }

   public int getInt(String key) {
      Integer i = getInteger(key, null);
      if (i != null) {
         return i.intValue();
      } else {
         throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
      }
   }

   public int getInt(String key, int defaultValue) {
      Integer i = getInteger(key, null);

      if (i == null) {
         return defaultValue;
      }

      return i.intValue();
   }

   public Integer getInteger(String key, Integer defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toInteger(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to an Integer object", e);
         }
      }
   }

   public long getLong(String key) {
      Long l = getLong(key, null);
      if (l != null) {
         return l.longValue();
      } else {
         throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
      }
   }

   public long getLong(String key, long defaultValue) {
      return getLong(key, new Long(defaultValue)).longValue();
   }

   public Long getLong(String key, Long defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toLong(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Long object", e);
         }
      }
   }

   public short getShort(String key) {
      Short s = getShort(key, null);
      if (s != null) {
         return s.shortValue();
      } else {
         throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
      }
   }

   public short getShort(String key, short defaultValue) {
      return getShort(key, new Short(defaultValue)).shortValue();
   }

   public Short getShort(String key, Short defaultValue) {
      String value = getProperty(key);

      if (value == null) {
         return defaultValue;
      } else {
         try {
            return PropertyConverter.toShort(interpolate(value));
         } catch (RuntimeException e) {
            throw new RuntimeException('\'' + key + "' doesn't map to a Short object", e);
         }
      }
   }


   public String getString(String key, String defaultValue) {
      String value = getProperty(key);
      if (value instanceof String) {
         return interpolate((String) value);
      } else {
         return interpolate(defaultValue);
      }
   }

   protected String interpolate(String value) {
      Object result = strSubstitutor.replace(value);
      return (result == null) ? null : result.toString();
   }

   protected ConfigurationInterpolator createInterpolator() {
      ConfigurationInterpolator interpol = new ConfigurationInterpolator();
      interpol.setDefaultLookup(new StrLookup()
      {
         @Override
         public String lookup(String var)
         {
            String prop = getProperty(var);
            return (prop != null) ? prop : null;
         }
      });
      return interpol;
   }

}
